#ifndef SCON_OPERATION_H_
#define SCON_OPERATION_H_

#include "scon_result.h"


#include <queue>

#include <process.h>

#ifdef __SCON_TEST__
#include "gtest/gtest_prod.h"
#endif

namespace SCon {

template<class ConnectionProfile> class SyncAsyncIO {
/*
Класс SyncAsyncIO предназначен для выполнения синхронных/асинхронных операций приема/передачи данных с соединением, характеристики которого содержатся в ConnectionProfile.
Конструкторы класса SyncAsyncIO пусты и ничего не выполняют. Привести объект класса в рабочее состояние можно функцией Initialize(), разрушить функцией Destroy().
*/

#ifdef __SCON_TEST__
	friend class OperationTest;
	FRIEND_TEST(OperationTest, SyncAsycnIO_RequestHandlerTest);
#endif

public:
	SyncAsyncIO (){}

/*
Функция
	void Initialize(const ConnectionProfile* cp = 0);
инициализирует объект. Запускает рабочий поток и активирует события.
Принимаемые параметры:
- cp - указатель на профиль устройства.
*/
	void Initialize(HANDLE deviceDescriptor);

/*
Функция
	void Destroy();
посылает рабочему потоку сообщение об окончании работы, дожидается пока завершатся все операции и уничтожает дескрипторы потока и событий.
*/
	void Destroy();

#ifndef __SCON_TEST__
protected:
	~SyncAsyncIO (){}
#endif

/*
Функция
	FutureResult AsyncRead(int dataSize, unsigned int* buffer = 0, std::string* errorMessage = 0);
запускает асинхронную операцию чтения. После окончания операции достоверный результат можно получить с помощью FutureResult::GetResult()
Принимаемые параметры:
- dataSize - количество байт данных, которые необходимо прочитать, должно быть больше нуля
- buffer - указатель на буффер в который можно прочитать данные, может быть равен 0. Тогда буффер выделится динамически внутри функции.
- errorMessage - указатель на строку куда можно сохранить текст ошибки, может быть равен 0
Возвращаемые параметры:
Объект FutureResult, в котором по окончании операции будет достоверный ее результат.
*/
	FutureResult AsyncRead(int dataSize, unsigned char* buffer = 0, std::string* errorMessage = 0);
	FutureResult& AsyncWrite(int dataSize, unsigned char* data, std::string errorMessage = 0);

private:
	struct Job{
		static const int READ = 0;
		static const int WRITE = 1;
		static const int FINISH = 2;

		unsigned char* buffer_;
		int dataSize_;
		long unsigned int* actualDataSize_;
		int operationType_;
		std::string* errorMessage_;

		Job(unsigned char* buffer,
			int dataSize,
			long unsigned int* actualDataSize,
			int operationType,
			std::string* errorMessage): buffer_(buffer),
							dataSize_(dataSize),
							actualDataSize_(actualDataSize),
							operationType_(operationType),
							errorMessage_(errorMessage)
		{}
	};

	HANDLE deviceDescriptor_;
	unsigned int workThreadDescriptor_;
	HANDLE request_;
	HANDLE operationIsDone_;
	std::queue<Job> jobQueue_;

/*
Функция
	unsigned int RequestHandler();
выполняет обработку запросов. должна быть статической чтобы можно было передать указатель на нее в функцию WINAPI _beginthreadex.
Кроме того в качестве параметра функции должен передаваться конкретный объект, чтобы можно было использовать его нестатические члены данных. Следовательно в функцию WINAPI _beginthreadex в качестве параметра для функции должен передаваться this.
*/
	static unsigned int __stdcall RequestHandler(void* param);

/*
Функция
	static void errorHandler(bool operationState, std::string* errorMessage);
является вспомогательной и предназначена для для генерации сообщений обо ошибках
Принимаемые параметры:
- operationState - булевский параметр, сигнализирующей об успешном(или нет) прохождении операции
- errorMessage - указатель на строку, куда можно сохранить текст ошибки в случае необходимости
*/
	static void ErrorHandler(bool operationState, std::string* errorMessage);
};//end of declaration class SyncAsyncIO

template<class ConnectionProfile>
void SyncAsyncIO<ConnectionProfile>::Initialize(HANDLE deviceDescriptor){
	deviceDescriptor_ = deviceDescriptor;
	unsigned int threadID;
	request_ = CreateEvent(NULL, FALSE, FALSE, NULL);
	operationIsDone_ = CreateEvent(NULL, FALSE, FALSE, NULL);
	workThreadDescriptor_ = _beginthreadex(NULL, 0, SyncAsyncIO<ConnectionProfile>::RequestHandler, this, 0, &threadID);
}//end of template<class ConnectionProfile> void SyncAsyncIO<ConnectionProfile>::Initialize(const ConnectionProfile* cp)

template<class ConnectionProfile> void SyncAsyncIO<ConnectionProfile>::Destroy() {
	jobQueue_.push(Job(0, 0, 0, 2, 0));
	WaitForSingleObject(reinterpret_cast<HANDLE>(workThreadDescriptor_), INFINITE);
	CloseHandle(reinterpret_cast<HANDLE>(workThreadDescriptor_));
	CloseHandle(request_);
	CloseHandle(operationIsDone_);
}//end of template<class ConnectionProfile> void SyncAsyncIO<ConnectionProfile::Destroy()

template<class ConnectionProfile> FutureResult SyncAsyncIO<ConnectionProfile>::AsyncRead(int dataSize, unsigned char* buf, std::string* errorMessage) {
	//FutureResult res;
	return FutureResult();
}//end of template<class ConnectionProfile> FutureResult SyncAsyncIO<ConnectionProfile>::AsyncRead()

template<class ConnectionProfile> unsigned int SyncAsyncIO<ConnectionProfile>::RequestHandler(void* object) {
	//в связи с тем, что в функцию WINAPI _beginthreadex можно передавать лишь указатели на функции,
	//которые принимают void* и возвращают unsigned int, передаваемый функции параметр необходимо интерпретировать самостоятельно
	SyncAsyncIO<ConnectionProfile>* concreteObject = reinterpret_cast<SyncAsyncIO<ConnectionProfile>*>(object);
	DWORD whatIsProblem;
	while(true) {
		whatIsProblem = WaitForSingleObject(concreteObject->request_, INFINITE);
		if (whatIsProblem == WAIT_FAILED) {
			break;
		}/* end of if */

		Job job = concreteObject->jobQueue_.front();
		concreteObject->jobQueue_.pop();
		if (job.operationType_ == Job::READ) {
			bool operationState = ReadFile(concreteObject->deviceDescriptor_, job.buffer_,  job.dataSize_, job.actualDataSize_, 0);
			ErrorHandler(operationState, job.errorMessage_);
			SetEvent(concreteObject->operationIsDone_);
		}/* end of if */

		if (job.operationType_ == Job::WRITE) {
			bool operationState = WriteFile(concreteObject->deviceDescriptor_, job.buffer_,  job.dataSize_, job.actualDataSize_, 0);
			ErrorHandler(operationState, job.errorMessage_);
			SetEvent(concreteObject->operationIsDone_);
		}/* end of if */

		if (job.operationType_ == Job::FINISH) {
			break;
		}/* end of if */
	}//end of while

	return 0;
}//end of template<class ConnectionProfile> DWORD SyncAsyncIO<ConnectionProfile>::RequestHandler()

template<class ConnectionProfile> void SyncAsyncIO<ConnectionProfile>::ErrorHandler(bool operationState, std::string* errorMessage) {
	if (operationState == true) {
		return;
	}/* end of if */

	if (operationState == false && errorMessage != 0) {
		int errorCode = GetLastError();
		char buf[500];
		FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, errorCode,
						MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US),
						buf, 500, NULL);
		*errorMessage = buf;
	}/* end of if */
}//end of template<class ConnectionProfile> void SyncAsyncIO<ConnectionProfile>::ErrorHandler()

} /* SCon */ 

#endif /* end of include guard: SCON_OPERATION_H_ */

